﻿Imports System.Runtime.CompilerServices
Imports System.Reflection
Imports System.Text
Imports Kairyu.DCEngine

''' <summary>
''' クエリ拡張メソッド
''' </summary>
''' <remarks></remarks>
Public Module IQueryExtensions

#Region "CascadeOption"
    ''' <summary>
    ''' 上層フルカスケードします
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function FullCascade(Of T)(source As Query(Of T)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")

        source.CascadeLevel = -1
        Return source
    End Function

    ''' <summary>
    ''' 指定されたレベルまで上層カスケードします
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="level"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function Cascade(Of T)(source As Query(Of T), level As Integer) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")

        source.CascadeLevel = level
        Return source
    End Function

    ''' <summary>
    ''' 内部結合のみカスケードします
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IncludeOuter(Of T)(source As Query(Of T)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")

        source.CanLeftJoin = True
        Return source
    End Function

    ''' <summary>
    ''' 外部結合もカスケードします
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function InnerOnly(Of T)(source As Query(Of T)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")

        source.CanLeftJoin = False
        Return source
    End Function
#End Region

#Region "[Where]"

    ''' <summary>
    ''' インスタンスから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T">自身の型</typeparam>
    ''' <param name="source">読み込み起点となるリスト</param>
    ''' <param name="instance">検索条件インスタンス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), instance As T) As Query(Of T)
        Return [Where](source, {instance})
    End Function

    ''' <summary>
    ''' インスタンスから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T">自身の型</typeparam>
    ''' <param name="source">読み込み起点となるリスト</param>
    ''' <param name="instances">検索条件インスタンス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), instances As IEnumerable(Of T)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If instances Is Nothing Then Throw New ArgumentNullException("instances")

        For Each item In instances
            Dim def = GetType(T).ToEntityDefinition
            Dim c = InstanceCondition.Create(item, def.KeyProperties)
            source.Conditions.Add(c)
        Next
        Return source
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="id"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), id As Integer) As Query(Of T)
        Return [Where](source, {id})
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="ids"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), ids As IEnumerable(Of Integer)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If ids Is Nothing Then Throw New ArgumentNullException("ids")

        Dim def = GetType(T).ToEntityDefinition
        If def.KeyProperties.Count <> 1 Then Throw New InvalidOperationException("主キーが1つでない場合、このメソッドは使用できません。")

        For Each item In ids
            Dim instance = Activator.CreateInstance(Of T)()
            def.KeyProperties.First.SetLiteralValue(instance, item)

            Dim c = InstanceCondition.Create(instance, def.KeyProperties)
            source.Conditions.Add(c)
        Next
        Return source
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="id"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), id As String) As Query(Of T)
        Return [Where](source, {id})
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="ids"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> <Obsolete("IdSearchに名称が変更になりました")>
    Public Function [Where](Of T)(source As Query(Of T), ids As IEnumerable(Of String)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If ids Is Nothing Then Throw New ArgumentNullException("ids")

        Dim def = GetType(T).ToEntityDefinition
        If def.KeyProperties.Count <> 1 Then Throw New InvalidOperationException("主キーが1つでない場合、このメソッドは使用できません。")

        For Each item In ids
            Dim instance = Activator.CreateInstance(Of T)()
            def.KeyProperties.First.SetLiteralValue(instance, item)

            Dim c = InstanceCondition.Create(instance, def.KeyProperties)
            source.Conditions.Add(c)
        Next
        Return source
    End Function
#End Region

#Region "IdSearch"
    ''' <summary>
    ''' インスタンスから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T">自身の型</typeparam>
    ''' <param name="source">読み込み起点となるリスト</param>
    ''' <param name="instance">検索条件インスタンス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), instance As T) As Query(Of T)
        Return IdSearch(source, {instance})
    End Function

    ''' <summary>
    ''' インスタンスから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T">自身の型</typeparam>
    ''' <param name="source">読み込み起点となるリスト</param>
    ''' <param name="instances">検索条件インスタンス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), instances As IEnumerable(Of T)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If instances Is Nothing Then Throw New ArgumentNullException("instances")

        For Each item In instances
            Dim def = GetType(T).ToEntityDefinition
            Dim c = InstanceCondition.Create(item, def.KeyProperties)
            source.Conditions.Add(c)
        Next
        Return source
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="id"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), id As Integer) As Query(Of T)
        Return IdSearch(source, {id})
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="ids"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), ids As IEnumerable(Of Integer)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If ids Is Nothing Then Throw New ArgumentNullException("ids")

        Dim def = GetType(T).ToEntityDefinition
        If def.KeyProperties.Count <> 1 Then Throw New InvalidOperationException("主キーが1つでない場合、このメソッドは使用できません。")

        'IN条件を設定
        Dim c = InCondition.Create(def.KeyProperties.First.LiteralName, ids)
        source.Conditions.Add(c)

        Return source
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="id"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), id As String) As Query(Of T)
        Return IdSearch(source, {id})
    End Function

    ''' <summary>
    ''' IDから主キー検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="ids"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function IdSearch(Of T)(source As Query(Of T), ids As IEnumerable(Of String)) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If ids Is Nothing Then Throw New ArgumentNullException("ids")

        Dim def = GetType(T).ToEntityDefinition
        If def.KeyProperties.Count <> 1 Then Throw New InvalidOperationException("主キーが1つでない場合、このメソッドは使用できません。")

        For Each item In ids
            Dim instance = Activator.CreateInstance(Of T)()
            def.KeyProperties.First.SetLiteralValue(instance, item)

            Dim c = InstanceCondition.Create(instance, def.KeyProperties)
            source.Conditions.Add(c)
        Next
        Return source
    End Function
#End Region

#Region "Search"
    ''' <summary>
    ''' 動的型付け変数から検索条件を設定します
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <param name="dynamicInstance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()>
    Public Function Search(Of T)(source As Query(Of T), dynamicInstance As Object) As Query(Of T)
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If dynamicInstance Is Nothing Then Throw New ArgumentNullException("dynamicInstance")

        'リテラルプロパティに変換する
        Dim instance = Activator.CreateInstance(Of T)()
        Dim props As New List(Of LiteralProperty)
        Dim def = GetType(T).ToEntityDefinition
        For Each item In dynamicInstance.GetType.GetProperties
            '検索対象はシステム名（加工処理前のリテラル名）
            Dim prop = (From el In def.LiteralProperties Where el.SystemName = item.Name).FirstOrDefault
            If prop Is Nothing Then Throw New ArgumentException(String.Format("システム名が見つかりません。（型：{0}、名前：{1}）", GetType(T).Name, item.Name))

            Dim val = item.GetValue(dynamicInstance, Nothing)

            '値の移し替え
            prop.SetValue(instance, val, Nothing)
            props.Add(prop)
        Next

        Dim condition = InstanceCondition.Create(instance, props)
        source.Conditions.Add(condition)

        Return source
    End Function
#End Region

    ''' <summary>
    ''' フェッチします
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="source"></param>
    ''' <remarks></remarks>
    <Extension()>
    Public Sub Fetch(Of T)(source As IQuery(Of T))
        If source Is Nothing Then Throw New ArgumentNullException("source")

        source.ToList()
    End Sub

End Module

